package SSF.OS.TCP.test;

/**
 * tcpServer.java
 * Created: Tue Nov 16 1999 ato, hongbol
 * Revised: Fri May 18 2001 ato
 *
 */

import com.renesys.raceway.SSF.*;
import SSF.OS.*;
import SSF.OS.TCP.*;
import SSF.OS.Socket.*;
import com.renesys.raceway.DML.*;
import SSF.Net.*;
import SSF.Net.Util.*;

/** A simple TCP server application provided as an example.
 *  The server configures itself from the DML file.
 *  The client-server protocol is absolutely minimal: the client
 *  sends to server's well known address just one integer that specifies
 *  the total amount of virtual data it requests. After receiving
 *  a client's request, the server spawns a slave server that 
 *  sends the virtual data to the client until
 *  the request is fulfilled.
 *  The default is no limit on the number of clients, unless the attribute
 *  client_limit is set. There
 *  is no limit on the number of servers that can coexist in a single Host
 *  as long as each tcpServer has a distinct wellKnownPort number.
 */

public class tcpServer extends ProtocolSession {

  /********************* Attribute Variables *********************/

  /** port number used by this server */
  int wellKnownPort;

  /** size of data request object from matching client */
  public int request_size;

  /** "listening" socket of this server */
  public tcpSocket lsd;

  /** max number of simultaneous client connections serviced
   *  byf this server - default is unlimited (Integer.MAX_VALUE).
   */
  public int client_limit = Integer.MAX_VALUE;

  /** ID of the latest client connection serviced by this server */
  public int clientNumber = 0;

  /** The maximum size of pending connection request queue, Qlimit,
   *  that determines when the server socket rejects connection
   *  requests.
   *  @see SSF.OS.TCP.tcpSocket
   */
  public int qlimit = 5;

  /** Host entity where this pseudo-protocol is installed */
  public Host localHost;

  /** Host's IP address */
  public int localIP;

  /** Host's NHI address */
  public String localNHI;

  /** Host's Socket ProtocolSession */
  public socketMaster socketms;

  /** default is don't print verbose debugging information */
  public boolean debug = false;

  /** default is show summary session information */
  public boolean show_report = true;

  /*************** The slave server inner class ***************/
 
  class slaveServer extends ProtocolSession {
    tcpServer owner;
    int nbytesRequest;
    tcpSocket sd;

    public slaveServer(tcpSocket ssd) {
      owner = tcpServer.this;
      sd = ssd;
    }

    Object[] obj;
    Continuation masterCont;

    void serviceRequest(Continuation caller) {
      masterCont = caller;
      obj = new Object[1];
      sd.read(obj, owner.request_size, new Continuation() {
        public void success() {
          nbytesRequest = ((Integer)obj[0]).intValue();
          if(debug)
            serverInfo("slave read(), request "+nbytesRequest+"B OK");
          sd.write(nbytesRequest, new Continuation() {
            public void success() { 
              // succeeded sending to client
              try {
                //sd.close(masterCont);
                sd.close(new Continuation() {
                  public void success() {
                    if(debug)
                      serverInfo("slave close() OK");
                  }
                  public void failure(int errno) {
                    if(debug)
                      serverInfo("slave close() FAILURE "
                                 +socketMaster.errorString(errno));
                  }
                });
                masterCont.success();
              } catch (ProtocolException e) {
                System.err.println(e);
              } 
            }
            public void failure(int errno) { // failed writing
              if(debug)
                serverInfo("slave write() "+nbytesRequest+"B failure");
              masterCont.failure(errno);
            }
          });
        }
        public void failure(int errno) { // failed reading clnt request
          if(debug)
            serverInfo("slave read() failure");
          masterCont.failure(errno);
        }
      });
    }

    public boolean push(ProtocolMessage message, ProtocolSession fromSession)
      throws ProtocolException {
        return false;
    }
  } // end of slaveServer

  /************************* Constructors ************************/

  public tcpServer() {}

  /************************ Class Methods ***********************/

  /** Server configuration. Supported DML attributes:
   * <PRE>
   *   ProtocolSession [ name server use SSF.OS.TCP.test.tcpServer
   *     port          %I     # server's well known port (int)
   *     client_limit  %I     # max number of simultaneous established client
   *                          # connections, if omitted, default is unlimited.
   *                          # If exceeded, connection request is ignored
   *                          # and left to time out.
   *
   *     queue_limit   %I     # maximum size of pending connection request
   *                          # queue, Qlimit, if omitted default is 5.
   *                          # Determines the total number of pending
   *                          # connection requests (see SSF.OS.TCP.tcpSocket).
   *                          # If exceeded, the listening socket sends RESET
   *                          # to client and drops the request.
   *
   *     request_size  %I     # nominal size of client request datagram (bytes, int)
   *                          # (must be the same for the client)
   *
   *     show_report   %S     # print client-server session summary report, true/false
   *     debug         %S     # print verbose server diagnostics, true/false
   *   ]
   * </PRE>
   *
   */
  public void config(Configuration cfg) throws configException {
    super.config(cfg);
    String str;

    if((str = (String)cfg.findSingle("port")) != null) {
      wellKnownPort = (new Integer(str)).intValue();
      if (wellKnownPort > 10000)
        serverInfo("Warning: Server port must be < 10,000.");
    }

    if((str = (String)cfg.findSingle("request_size")) != null)
      request_size = (new Integer(str)).intValue();

    if((str = (String)cfg.findSingle("client_limit")) != null)
      client_limit = (new Integer(str)).intValue();

    if((str = (String)cfg.findSingle("queue_limit")) != null)
      qlimit = (new Integer(str)).intValue();

    str = (String)cfg.findSingle("debug");
    if((str != null) && (str.compareTo("true") == 0))
      debug =  true;

    str = (String)cfg.findSingle("show_report");
    if((str != null) && (str.compareTo("false") == 0))
      show_report = false;
  }

  Object[] obj;

  public void init(){
    localHost = (SSF.Net.Host)inGraph();
    localNHI = localHost.nhi;
    try {
      socketms = (socketMaster)localHost.SessionForName("socket");
      lsd = (tcpSocket)socketms.socket(this, "tcp");
      localIP = ((NIC)((IP)localHost.SessionForName("ip"))
                   .INTERFACES.elementAt(0)).ipAddr;
    }catch (ProtocolException e) {
      System.err.println("tcpServer: " + e);
    } 
    lsd.bind(localIP, wellKnownPort);
    // in release 1.2 was lsd.listen(client_limit)
    lsd.listen(qlimit);
    obj = new Object[1];
    serv();
  }

  socketAPI[] new_socket;

  public void serv() {
    new_socket = new socketAPI[1];
    try {
      lsd.accept(new_socket, new Continuation() {     // signal from socket
        public void success() {
          if(++clientNumber > client_limit) {
            if(debug)
              serverInfo("max # established client connections exceeded, ignoring conn. request");
            --clientNumber;
          } else {
            slaveServer ss = new slaveServer((tcpSocket)new_socket[0]);
            ss.serviceRequest(new Continuation() {   // signal from slaveServer
              public void success() {
                // socket closed
                --clientNumber;
                if(debug)
                  serverInfo("serviceRequest() OK");
              }
              public void failure(int errno) {
                if(debug)
                  serverInfo("serviceRequest FAILURE: "
                             +socketMaster.errorString(errno));
                --clientNumber;
              }
            });
          }
          serv();
        }
        public void failure(int errno) {
          // client connection request failed before servicing
          if(debug)
            serverInfo("accept() FAILURE: "
                       +socketMaster.errorString(errno));
          serv();
        }
      });
    } catch (ProtocolException e) {
      System.err.println(e);
    } 
  }  

  /** preamble to server diagnostics */
  void serverInfo(String str){
    System.out.println(localHost.now()/(double)SSF.Net.Net.seconds(1.0)
            + " tcpServer " + localNHI + " " 
            + IP_s.IPtoString(localIP) + ":" + wellKnownPort + " " + str);
  }

  public boolean push(ProtocolMessage message, ProtocolSession fromSession)
    throws ProtocolException {
    return false;
  }
}
